package org.bes.WinClass;

import org.bes.api.IUpdate;
import org.bes.utils.JarUtils;
import org.bes.utils.StrPathUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.json.*;
import java.io.*;
import java.nio.channels.FileChannel;

import java.nio.file.*;
import java.util.*;
import java.util.jar.*;

import java.util.stream.Stream;


import static org.bes.WinClass.FileManager.*;
import static org.bes.WinClass.FileManager.addVersionFileMap;
import static org.bes.WinClass.Rollbacker.backupVersion;
import static org.bes.WinClass.Rollbacker.versionData;
import static org.bes.utils.StrPathUtils.*;

public class UpdatePatch extends PatchInfo implements IUpdate {

    private static final Logger log = LoggerFactory.getLogger(UpdatePatch.class);
    private static boolean isAllCover = false;
    private List<File> allFilesToUpdate = new ArrayList<>();
    public static List<JarFileInfo> jarFileInfoList = new ArrayList<>();
    // 用于存储所有操作的记录
    public static List<FileOperation> operationRecords = new ArrayList<>();


    public UpdatePatch() {
        super();
    }

    public int update() throws IOException {


        // 2. 遍历所有需要处理的文件
        for (Map.Entry<String, File> entry : fileListQueue) {

            String symbol = entry.getKey();
            File file = entry.getValue();

            Path originRelativePath = FileManager.getBackupDir().relativize(file.toPath());
            Path originFilePath = FileManager.getSourceDir().resolve(stripFirstPart(originRelativePath)); // 原文件路径

            // 3. 如果是更新文件 (+)，执行更新操作
            if ("+".equals(symbol)) {

                if (file.getName().endsWith(".jar")) {
                    log.info("更新 JAR 中文件: " + file);
                    processJarFiles(file, originFilePath);
                    //FileManager.addVersionFileMap(file);
                } else {
                    // 常规文件更新: 替换整个文件
                    Files.copy(file.toPath(), originFilePath, StandardCopyOption.REPLACE_EXISTING);
                    FileManager.upgradedFilePaths.add(getRelavtivePath(file));
                    //FileManager.addVersionFileMap(file);
                    // 记录更新动作
                    addFileOperation(StrPathUtils.stripFirstPart(getBackupDir().relativize(file.toPath())), "update", getFileVersion(file));

                    log.info(file.getName() + " has been updated to the new version.");
                }

            } else if ("-".equals(symbol)) { // 4. 如果是删除文件 (-)，执行删除操作
                if (file.exists() && file.isFile()) {
                    // 读取 JSON 文件，如果是 JSON 格式，则需要额外的逻辑
                    if (file.getName().endsWith(".json")) {
                        jsonUpdate(file, "update");
                    }
                } else {
                    log.warn("尝试删除不存在的文件: " + file.getAbsolutePath());
                }
            } else {
                log.error("文件不存在或无效: " + file.getAbsolutePath() + ", 回滚补丁.");
                return 2;
            }
        }


        // 5. 最后统一记录所有操作
        VersionControl.logOperations(operationRecords);

        return 1;
    }


    // 将文件操作添加到操作记录中
    private static void addFileOperation(Path relativePath, String operationType, String version) {
        //FileOperation{file='D:\work\Project\test\test-apache\apache-tomcat-10.1.34\patch\backup\apache-patch-V1.1.0\conf\tomcat-users.xml', operation='update', version='1.1.0'}

        operationRecords.add(new FileOperation(relativePath, operationType, version));
    }


    // 处理 JSON 文件中的备份逻辑
    public static void jsonUpdate(File file, String type) throws FileNotFoundException {
        // Log the JSON file path
        log.info("Found JSON file: {}", file.getAbsolutePath());

        try (JsonReader jsonReader = Json.createReader(new FileReader(file))) {
            JsonObject rootObject = jsonReader.readObject();
            JsonArray deleteFilesArray = rootObject.getJsonArray("delete_files");

            // Step 1: Process the delete_files array
            for (JsonValue fileValue : deleteFilesArray) {
                // Check if the file entry is a regular file path or a JAR object
                if (fileValue.getValueType() == JsonValue.ValueType.STRING) {
                    // Regular file, process as before
                    String relativePath = fileValue.toString().replace("\"", "");  // Remove quotes around path
                    Path absolutePath = FileManager.getSourceDir().resolve(relativePath);  // Resolve to absolute path
                    FileDelete.deleteFile(absolutePath.toFile());

                    if("update".equals(type)){
                        // FileManager.addVersionFileMap(new File(file.getParentFile(), relativePath));
                        // 记录删除动作
                        addFileOperation(Path.of(relativePath), "remove", getFileVersion(file));
                    } else if ("rollback".equals(type)) {
                        versionData.put(relativePath, backupVersion);
                    }

                } else if (fileValue.getValueType() == JsonValue.ValueType.OBJECT) {
                    // Handle the case where fileValue is a JSON object containing a JAR file and classes to delete
                    JsonObject fileObject = (JsonObject) fileValue;

                    String relativePath = fileObject.getString("jar_file");
                    if("update".equals(type)){
                        //addVersionFileMap(new File(file.getParentFile(), relativePath));

                    } else if ("rollback".equals(type)) {
                        versionData.put(relativePath, backupVersion);
                    }

                    if (fileObject.containsKey("jar_file") && fileObject.containsKey("delete_classes")) {
                        String jarFilePath = fileObject.getString("jar_file");
                        JsonArray deleteClasses = fileObject.getJsonArray("delete_classes");

                        // Step 2: Process the JAR file
                        Path jarAbsolutePath = FileManager.getSourceDir().resolve(jarFilePath);  // Resolve JAR path
                        File jarFile = jarAbsolutePath.toFile();

                        if (jarFile.exists() && jarFile.isFile()) {
                            // Proceed with deleting classes from JAR file
                            log.info("Processing JAR file: {}", jarFilePath);
                            FileDelete.deleteClassesFromJar(jarFile, deleteClasses);

                            // Record JAR file operation
                            FileOperation jarFileOperation = new FileOperation(Path.of(jarFilePath), "remove", getFileVersion(file));

                            //save Jar information
                            //List<Path> deleteClassesList = new ArrayList<>();
                            for (JsonValue classValue : deleteClasses) {
                                // 获取文件路径字符串并转化为 Path 对象
                                String classFilePath = classValue.toString().replace("\"", "");  // Remove quotes around path
                                Path classFilePathAsPath = Paths.get(classFilePath);
                                //deleteClassesList.add(classFilePathAsPath);

                                // Add JarFileOperation for each deleted class
                                JarFileOperation jarClassOp = new JarFileOperation(classFilePathAsPath.toString(), "remove", getFileVersion(file));
                                jarFileOperation.addJarFileOperation(jarClassOp);
                            }

                            operationRecords.add(jarFileOperation);

                        } else {
                            log.warn("JAR file not found: {}", jarFilePath);
                        }
                    }
                }

            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    public static int processJarFiles(File patchFile, Path originFilePath) throws IOException {
        // 临时文件夹路径
        Path tempDir = FileManager.getTmpJarExtractDir(); // 获取临时目录
        Path originDir = tempDir.resolve("origin");
        Path patchDir = tempDir.resolve("patch");

        // 确保临时文件夹存在
        Files.createDirectories(originDir);
        Files.createDirectories(patchDir);

        //清空origin和patch文件夹内容
        clearDirectory(originDir);
        clearDirectory(patchDir);

        // 1. 解压源文件的 JAR 到 origin 文件夹
        extractJar(originFilePath.toFile(), originDir);

        // 2. 解压补丁文件的 JAR 到 patch 文件夹
        extractJar(patchFile, patchDir);

        // 3. 替换源文件夹中的文件为补丁文件夹中的文件
        if(replaceFilesInJar(originDir, patchDir, patchFile.toPath()) == 2){
            return 2;
        }

        // 4. 重新将更新后的 origin 文件夹打包成 JAR 文件
        String updatedJarPath = originFilePath.toString(); // 目标 JAR 文件路径
        repackageJar(originDir, updatedJarPath);

        log.info("JAR 文件更新完成: " + patchFile.getName());
        return 1;
    }


    private static void clearDirectory(Path dir) throws IOException {
        if (Files.exists(dir) && Files.isDirectory(dir)) {
            Files.walk(dir)
                    .filter(path -> !path.equals(dir))  // 避免删除目录本身
                    .sorted(Comparator.reverseOrder())  // 先删除子文件/文件夹
                    .forEach(path -> {
                        try {
                            Files.delete(path);
                        } catch (IOException e) {
                            throw new RuntimeException("无法删除文件: " + path, e);
                        }
                    });
        }
    }

    private static void extractJar(File jarFile, Path destinationDir) throws IOException {
        // 解压 JAR 文件到指定目录
        try (JarFile jar = new JarFile(jarFile)) {
            Enumeration<JarEntry> entries = jar.entries();
            byte[] buffer = new byte[8192];  // 增大缓冲区为 8KB

            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                Path entryPath = destinationDir.resolve(entry.getName());

                // 确保文件所在的上级目录存在
                Path parentDir = entryPath.getParent();
                if (parentDir != null && !Files.exists(parentDir)) {
                    Files.createDirectories(parentDir);
                }

                if (entry.isDirectory()) {
                    Files.createDirectories(entryPath);
                } else {
                    try (InputStream is = jar.getInputStream(entry);
                         OutputStream os = Files.newOutputStream(entryPath)) {
                        int bytesRead;
                        while ((bytesRead = is.read(buffer)) != -1) {
                            os.write(buffer, 0, bytesRead);
                        }
                    }
                }
            }
        }
    }



    private static int replaceFilesInJar(Path originDir, Path patchDir, Path patchFilePath) throws IOException {

        try (Stream<Path> patchFiles = Files.walk(patchDir)) {
            List<Path> fileList = patchFiles.filter(Files::isRegularFile).toList();

            for (Path patchFile : fileList) {
                try {
                    Path relativePath = patchDir.relativize(patchFile);
                    Path originFile = originDir.resolve(relativePath);
                    Files.createDirectories(originFile.getParent());

                    copyFile(patchFile, originFile);

                    log.debug("已替换/新增文件: " + relativePath);
                } catch (IOException e) {
                    log.error("替换文件失败: " + patchFile, e);
                }
            }

            FileOperation jarFileOperation = new FileOperation(stripFirstPart(getBackupDir().relativize(patchFilePath)), "update", getFileVersion(patchFilePath.toFile()));

            for (Path patchFile : fileList) {
                Path relativePath = patchDir.relativize(patchFile);
                // Add JarFileOperation for each deleted class
                JarFileOperation jarClassOp = new JarFileOperation(relativePath.toString(), "update", getFileVersion(patchFilePath.toFile()));
                jarFileOperation.addJarFileOperation(jarClassOp);
            }

            operationRecords.add(jarFileOperation);


        }
        return 1;  // 处理成功

    }

    // 读取 JSON 文件中的删除文件路径
    private static void copyFile(Path source, Path destination) throws IOException {
        try (FileChannel sourceChannel = FileChannel.open(source, StandardOpenOption.READ);
             FileChannel destinationChannel = FileChannel.open(destination, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
            long position = 0;
            long size = sourceChannel.size();
            while (position < size) {
                position += sourceChannel.transferTo(position, 1024 * 1024, destinationChannel);  // 1MB 每次传输
            }
        }
    }

    public List<String> readFilesToDeleteFromJson(File jsonFile) throws IOException {
        List<String> filesToDelete = new ArrayList<>();
        try (FileReader reader = new FileReader(jsonFile);
             JsonReader jsonReader = Json.createReader(reader)) {
            JsonObject jsonObject = jsonReader.readObject();
            JsonArray deleteFilesArray = jsonObject.getJsonArray("delete_files");
            if (deleteFilesArray != null) {
                for (JsonValue value : deleteFilesArray) {
                    if (value.getValueType() == JsonValue.ValueType.STRING) {
                        filesToDelete.add(value.toString().replace("\"", ""));
                    }
                }
            }
        } catch (JsonException e) {
            log.error("解析 JSON 文件失败: " + jsonFile.getAbsolutePath(), e);
            throw new IOException("解析 JSON 文件失败", e);
        }
        return filesToDelete;
    }

    private static void repackageJar(Path originDir, String updatedJarPath) throws IOException {
        // 将更新后的文件夹重新打包成 JAR 文件
        try (JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(updatedJarPath))) {
            Files.walk(originDir).forEach(file -> {
                try {
                    if (Files.isDirectory(file)) {
                        return; // 跳过目录
                    }

                    Path relativePath = originDir.relativize(file);
                    JarEntry entry = new JarEntry(relativePath.toString().replace("\\", "/"));
                    jarOut.putNextEntry(entry);
                    Files.copy(file, jarOut);
                    jarOut.closeEntry();

                    log.debug("打包文件: " + relativePath);
                } catch (IOException e) {
                    log.error("打包文件失败: " + file, e);
                }
            });
        }
    }

    @Override
    public int conflictFile(File file) throws IOException {
        String choice;
        if (isAllCover) {
            choice = "1";
        } else {

            log.warn("File conflict: " + file.getName() + " has been updated.");

            log.info("1: Overwrite the current file");
            log.info("2: Overwrite all files for subsequent conflicts");
            log.info("3: Skip the current file");
            log.info("4: Rollback all files");
            log.info("Please select an action:");
            Scanner scanner = new Scanner(System.in);
            choice = scanner.nextLine();
        }

        switch (choice) {
            case "1":
                Files.copy(file.toPath(), FileManager.getSourceDir().resolve(getRelavtivePath(file)), StandardCopyOption.REPLACE_EXISTING);
                log.info(file.getName() + " has been updated to the new version.");
                addVersionFileMap(file);
                break;
            case "2":
                isAllCover = true;
                Files.copy(file.toPath(), FileManager.getSourceDir().resolve(getRelavtivePath(file)), StandardCopyOption.REPLACE_EXISTING);
                log.info(file.getName() + " has been updated to the new version.");
                addVersionFileMap(file);
                break;
            case "3":
                log.info(file.getName() + " patch skipped.");
                break;
            case "4":
                return 2;
            default:
                log.warn("Invalid input, please try again.");
                conflictFile(file);
                break;
        }

        return 1;
    }
}
